package ru.amse.tsyganov.jumleditor.validator;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ru.amse.tsyganov.jumleditor.model.common.Edge;
import ru.amse.tsyganov.jumleditor.model.common.State;
import ru.amse.tsyganov.jumleditor.view.SelectedItems;
import ru.amse.tsyganov.jumleditor.view.Validity;
import ru.amse.tsyganov.jumleditor.view.ViewVisitor;
import ru.amse.tsyganov.jumleditor.view.activity.ActionStateView;
import ru.amse.tsyganov.jumleditor.view.activity.FinalStateView;
import ru.amse.tsyganov.jumleditor.view.activity.ForkJoinStateView;
import ru.amse.tsyganov.jumleditor.view.activity.InitialStateView;
import ru.amse.tsyganov.jumleditor.view.common.EdgeView;
import ru.amse.tsyganov.jumleditor.view.common.StateView;
import ru.amse.tsyganov.jumleditor.view.preferences.Images;

public class ActivityGraphValidationVisitor extends ViewVisitor {
	
	private static Map<Class, TransitionsAmount> outTransAmount =
		new HashMap<Class, TransitionsAmount>();
	
	private static Map<Class, TransitionsAmount> inTransAmount =
		new HashMap<Class, TransitionsAmount>();
	
	private static Map<Class, TransitionsAmount> sumTransAmount =
		new HashMap<Class, TransitionsAmount>();
	
	static {
		outTransAmount.put(ActionStateView.class, TransitionsAmount.ONE);
		inTransAmount.put(ActionStateView.class, TransitionsAmount.ONE);
		
		outTransAmount.put(FinalStateView.class, TransitionsAmount.NULL);
		inTransAmount.put(FinalStateView.class, TransitionsAmount.MORE_THAN_NULL);
		
		outTransAmount.put(InitialStateView.class, TransitionsAmount.ONE);
		inTransAmount.put(InitialStateView.class, TransitionsAmount.NULL);
		
		outTransAmount.put(ForkJoinStateView.class, TransitionsAmount.MORE_THAN_NULL);
		inTransAmount.put(ForkJoinStateView.class, TransitionsAmount.MORE_THAN_NULL);
		sumTransAmount.put(ForkJoinStateView.class, TransitionsAmount.MORE_THAN_TWO);
	}
	
	private final ProblemsList problems;
	
	private StateView<? extends State> initialStateView = null;
	
	private Problem multipleInitialStates;
	
	public ActivityGraphValidationVisitor(ProblemsList problems) {
		this.problems = problems;
	}
	
	public void visitStateView(StateView<? extends State> s) {
		validateState(problems, s);
	}
	
	public void visitEdgeView(EdgeView<? extends Edge> t) {
		t.setValidity(Validity.NORMAL);
		List<Problem> innerProblems = t.getInnerProblems();
		for (Problem p : innerProblems) {
			problems.addProblem(p);
		}
	}

	private void validateState(final ProblemsList problems, 
			StateView<? extends State> s) {
		
		s.setValidity(Validity.NORMAL);
		
		if (s instanceof InitialStateView) {
			if (initialStateView == null) {
				initialStateView = s;
			} else {
				if (multipleInitialStates == null) {
					SelectedItems problemSpace = new SelectedItems();
					problemSpace.selectedStatesAdd(s);
					problemSpace.selectedStatesAdd(initialStateView);
					
					initialStateView.setValidity(Validity.ERROR);
					s.setValidity(Validity.ERROR);
					
					String message = Validator.ERROR + 
						": initial state should be exactly one";
					
					multipleInitialStates = new Problem(message, 
							problemSpace, Images.getErrorImage());
					
					problems.addProblem(multipleInitialStates);
				} else {
					multipleInitialStates.getProblemPlace().
						selectedStatesAdd(s);
				}
			}
		}
					
		int outgoingTransitionsAmount = s.getModel().
			getStateVertex().outgoingTransitionsSize();
		
		int incomingTransitionsAmount = s.getModel().
			getStateVertex().incomingTransitionsSize();
					
		validateOutgoingTransitionsAmount(problems, s, outgoingTransitionsAmount);
		
		validateIncomingTransitionsAmount(problems, s, incomingTransitionsAmount);
		
		validateTransitionSum(problems, s, 
				outgoingTransitionsAmount, incomingTransitionsAmount);
		
		List<Problem> innerProblems = s.getInnerProblems();
		for (Problem p : innerProblems) {
			problems.addProblem(p);
		}
	}

	private void validateTransitionSum(final ProblemsList problems, 
			StateView<? extends State> s, int outgoingTransitionsAmount, 
			int incomingTransitionsAmount) {
		
		TransitionsAmount prefferedSumAmount = 
			sumTransAmount.get(s.getClass());
		
		if (prefferedSumAmount != null &&
			!prefferedSumAmount.check(
				incomingTransitionsAmount + 
					outgoingTransitionsAmount) &&
					s.getValidity() == Validity.NORMAL) {
			
			SelectedItems problemSpace = new SelectedItems();
			problemSpace.selectedStatesAdd(s);
			
			String message = Validator.WARNING +  
				": number of incoming and outgoing transition should be " + 
				prefferedSumAmount.toString();
			
			problems.addProblem(new Problem(message, 
					problemSpace, Images.getWarningImage()));
			
			s.setValidity(Validity.WARNING);
		}
	}

	private void validateIncomingTransitionsAmount(final ProblemsList problems, StateView<? extends State> s, int incomingTransitionsAmount) {
		TransitionsAmount allowedIncomingTransitionsAmount =
			inTransAmount.get(s.getClass());
		
		if (allowedIncomingTransitionsAmount != null &&
			!allowedIncomingTransitionsAmount.check(
				incomingTransitionsAmount)) {
			
			s.setValidity(Validity.ERROR);
			
			SelectedItems problemSpace = new SelectedItems();
			problemSpace.selectedStatesAdd(s);
			
			String message = Validator.ERROR +
				": number of incoming transition should be " + 
				allowedIncomingTransitionsAmount.toString();
			
			problems.addProblem(new Problem(message, 
					problemSpace, Images.getErrorImage()));
		}
	}

	private void validateOutgoingTransitionsAmount(
			final ProblemsList problems, 
			StateView<? extends State> s, 
			int outgoingTransitionsAmount) {
		
		TransitionsAmount allowedOutgoingTransitionsAmount =
			outTransAmount.get(s.getClass());
		
		if (allowedOutgoingTransitionsAmount != null &&
			!allowedOutgoingTransitionsAmount.check(
				outgoingTransitionsAmount)) {
			
			s.setValidity(Validity.ERROR);
			
			SelectedItems problemSpace = new SelectedItems();
			problemSpace.selectedStatesAdd(s);
			
			String message = Validator.ERROR +  
				": number of outgoing transition should be " + 
				allowedOutgoingTransitionsAmount.toString();
			
			problems.addProblem(new Problem(message, 
					problemSpace, Images.getErrorImage()));
		}
	}
	
	public boolean initialStateExists() {
		return initialStateView != null;
	}

	public StateView<? extends State> getInitialStateView() {
		return initialStateView;
	}
}